//-----------------------------------------------------------------------
// <copyright file="ModelObjectFormDefiner.cs" company="Jamie Knight">
// Copyright (c) Jamie Knight. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Reflection;
using Humanizer;
using DevKnightlie.WPF.AutoForm.Interfaces;
using System.Windows;
using DevKnightlie.WPF.AutoForm.FieldDefinitions;
using System.Windows.Input;

namespace DevKnightlie.WPF.AutoForm
{

    /// <summary>
    /// Arguments class passed to field definition events
    /// </summary>
    public class FieldDefinitionEventArgs : EventArgs
    {
        /// <summary>
        /// Holds the field definition
        /// </summary>
        public BaseFieldDefinition FieldDefinition { get; set; }

        /// <summary>
        /// Property Name
        /// </summary>
        public String PropertyName { get; private set; }

        /// <summary>
        /// If set to true by a FieldDefinitionRequired handler, prevents an input control from being created
        /// </summary>
        public bool Cancel { get; set; }

        public FieldDefinitionEventArgs(BaseFieldDefinition fieldDefinition)
            : this(fieldDefinition, "")
        {
        }

        public FieldDefinitionEventArgs(String propertyName)
            : this(null, propertyName)
        {
        }

        public FieldDefinitionEventArgs(BaseFieldDefinition fieldDefinition, String propertyName)
        {
            FieldDefinition = fieldDefinition;
            PropertyName = propertyName;
        }
    }

    /// <summary>
    /// Builds an AutoForm field definition list from a data model object
    /// </summary>
    public class ModelObjectFormDefiner : DependencyObject, IModelObjectFormDefiner
    {

        #region Private fields

        private FieldDefinitionList _fieldDefinitions = new FieldDefinitionList();
        private FieldDefinitionRegistry _definitionRegistry;

        #endregion


        #region Properties

        #region Dependency Properties

        public static readonly DependencyProperty ModelObjectProperty =
    DependencyProperty.Register("ModelObject", typeof(Object), typeof(ModelObjectFormDefiner),
    new FrameworkPropertyMetadata(2, FrameworkPropertyMetadataOptions.AffectsMeasure));

        /// <summary>
        /// Model Object to build form from
        /// </summary>
        public Object ModelObject
        {
            get { return (Object)GetValue(ModelObjectProperty); }
            set
            {
                SetValue(ModelObjectProperty, value);
                _fieldDefinitions.Clear();
            }
        }

        #endregion

        public delegate void FieldDefinitionEvent(object sender, FieldDefinitionEventArgs e);


        private FieldDefinitionEvent _fieldDefinitionRequired;

        /// <summary>
        /// Fired when a field definition is required. Gives a handler the opportunity to 
        /// provide a different definition from the one in the definition registry. The 
        /// handler can set the event args Cancelled property to true to skip the named property
        /// </summary>
        public event FieldDefinitionEvent FieldDefinitionRequired
        {
            add { _fieldDefinitionRequired += value; }
            remove { _fieldDefinitionRequired -= value; }
        }


        private FieldDefinitionEvent _fieldDefinitionCreated;

        /// <summary>
        /// Fired when a field definition has been created.  Allows for customization of properties before the input control is created
        /// </summary>
        public event FieldDefinitionEvent FieldDefinitionCreated
        {
            add { _fieldDefinitionCreated += value; }
            remove { _fieldDefinitionCreated -= value; }
        }


        /// <summary>
        /// Holds a reference to the field definition registry this definer should use
        /// </summary>
        public FieldDefinitionRegistry DefinitionRegistry
        {
            get
            {
                return _definitionRegistry;
            }
            private set
            {
                _definitionRegistry = value;
            }
        }


        #endregion


        #region Constructor

        /// <summary>
        /// Constructs a new instance of ModelObjectFormDefiner
        /// </summary>
        /// <param name="definitionRegistry">FieldDefinitionRegistry to use, or null for Singleton</param>
        public ModelObjectFormDefiner(FieldDefinitionRegistry definitionRegistry = null)
        {
            if (definitionRegistry == null)
            {
                DefinitionRegistry = FieldDefinitionRegistry.Default;
            }
            else
            {
                DefinitionRegistry = definitionRegistry;
            }
        }


        /// <summary>
        /// Constructs a new instance of ModelObjectFormDefiner with the default FieldDefinitionRegistry
        /// </summary>
        public ModelObjectFormDefiner()
            : this(null)
        {

        }

        #endregion


        /// <summary>
        /// Build a list of FieldDefinition objects based on the supplied data model object
        /// </summary>
        /// <returns>IEnumerable of FieldDefinition</returns>
        public IEnumerable<BaseFieldDefinition> FieldDefinitionList()
        {
            if (_fieldDefinitions.Count == 0 && ModelObject != null)
            {
                BuildFieldDefinitionList();
            }
            return _fieldDefinitions;
        }


        /// <summary>
        /// Re-populate the field definition list from the model object
        /// </summary>
        private void BuildFieldDefinitionList()
        {
            _fieldDefinitions.Clear();
            if (ModelObject == null)
            {
                return;
            }
            PropertyInfo[] propertyInfo = ModelObject.GetType().GetProperties();
            foreach (PropertyInfo propInfo in propertyInfo)
            {
                // GetSetMethod() returns null for a property whose setter is private
                if ((!propInfo.CanWrite || propInfo.GetSetMethod() == null) &&
                    !typeof (ICommand).IsAssignableFrom(propInfo.PropertyType))
                {
                    continue;                    
                }
                BaseFieldDefinition fieldDefinition = null;
                Type T = propInfo.PropertyType;
                bool skipProperty = false;
                LookForFieldDefinition(propInfo, ref fieldDefinition, ref skipProperty);
                if (skipProperty)
                {
                    continue;
                }
                if (fieldDefinition == null)
                {
                    if (typeof(ICommand).IsAssignableFrom(propInfo.PropertyType))
                    {
                        // This is a Command
                        fieldDefinition = new CommandButtonFieldDefinition()
                        {
                            PropertyInfo = propInfo,
                            Command = ModelObject.GetType().GetProperty(propInfo.Name).GetValue(ModelObject, null) as ICommand
                        };
                    }
                    else if (T.IsEnum)
                    {
                        // Enums get a combo box
                        fieldDefinition = new ComboBoxFieldDefinition()
                        {
                            PropertyInfo = propInfo
                        };
                    }
                    else
                    {
                        fieldDefinition = FieldDefinitionRegistry.Default.Definition(T, propInfo.Name);
                        fieldDefinition.PropertyInfo = propInfo;
                    }
                }

                if (fieldDefinition == null) continue;

                fieldDefinition.PropertyName = propInfo.Name;
                fieldDefinition.Title = propInfo.Name.Humanize();

                if (_fieldDefinitionCreated != null)
                {
                    _fieldDefinitionCreated(this, new FieldDefinitionEventArgs(fieldDefinition, propInfo.Name));
                }
                _fieldDefinitions.Add(fieldDefinition);
            }
        }

        /// <summary>
        /// Calls the FieldDefinitionRequired event handler if it is set. Descendants can create and configure their own definitions here
        /// </summary>
        protected virtual void LookForFieldDefinition(PropertyInfo propinfo, ref BaseFieldDefinition fieldDefinition, ref bool cancelProperty)
        {
            if (_fieldDefinitionRequired != null)
            {
                // Call the event handler
                FieldDefinitionEventArgs args = new FieldDefinitionEventArgs(propinfo.Name);
                _fieldDefinitionRequired(this, args);
                if (args.Cancel)
                {
                    // Handler has cancelled this property
                    fieldDefinition = null;
                    cancelProperty = true;
                }
                else if (args.FieldDefinition != null)
                {
                    fieldDefinition = args.FieldDefinition;
                    fieldDefinition.PropertyInfo = propinfo;
                }
            }
        }

    }
}
